/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using QName = System.Qizx.Api.QName;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using Utils = System.Qizx.Util.Basic.Utils;
using IQName = System.Qizx.Xdm.IQName;
using Function = System.Qizx.XQuery.Fn.Function;
using JavaFunction = System.Qizx.XQuery.Fn.JavaFunction;
using _Boolean = System.Qizx.XQuery.Fn._Boolean;
using _Empty = System.Qizx.XQuery.Fn._Empty;
using _String = System.Qizx.XQuery.Fn._String;
using _QName = System.Qizx.XQuery.Fn._QName;

namespace System.Qizx.XQuery {

    /// <summary> Management of predefined functions and variables. 
    /// Features an automatic registration of functions.
    /// Because it manages Java binding security, one instance per XQuery session
    /// (shared among Expressions).
    /// </summary>
    public class PredefinedModule : ModuleContext {
        private static string funPackage;

        private PredefinedModule.FunctionPlugger[] pluggers = new PredefinedModule.FunctionPlugger[0];

        private JavaFunction.Plugger javaPlugger;

        /// <summary> Mechanism to dynamically load/plug functions: When an unknown function
        /// is encountered by the parser, it calls
        /// PredefinedModule.localFunctionLookup which in turn asks its registered
        /// FunctionPluggers to look for this function. This mechanism manages
        /// predefined functions fn: (with the side benefit that the system start-up
        /// is faster because functions are lazily loaded), type functions (xs: and
        /// xdt:), Java extensions, and application extensions.
        /// </summary>
        public interface FunctionPlugger {
            Function plug(QName functionName, PredefinedModule target);
        }

        public PredefinedModule()
            : base(null) {
            // fn:functions: name is camelCased,
            // eg get-year-from-date -> GetYearFromDate
            registerFunctionPlugger(new BasicFunctionPlugger(NamespaceContext.FN, funPackage + ".%C"));
            // Type functions: like fn:functions but the name is left untouched
            registerFunctionPlugger(new BasicFunctionPlugger(NamespaceContext.XSD, funPackage + ".XS_%n"));
            // registerFunctionPlugger(new BasicFunctionPlugger(Namespace.XDT,
            // funPackage + ".XS_%n"));
            // Java binding:
            registerFunctionPlugger(javaPlugger = new JavaFunction.Plugger());

            try {
                // explicit declaration of functions with problematic names:
                declareFunction(new _Boolean());
                declareFunction(new _String());
                declareFunction(new _Empty());
                declareFunction(new _QName());
                // predefined global var $arguments contains command line options
                defineGlobal(IQName.GetQName("arguments"), XQType.STRING.star);
            } catch (System.Exception e) {
                SupportClass.WriteStackTrace(e, Console.Error);
            }

            importedModules.Clear(); // final
            predefined = null;
        }

        /// <summary> Used by applications:</summary>
        public virtual void registerFunctionPlugger(PredefinedModule.FunctionPlugger plugger) {
            PredefinedModule.FunctionPlugger[] old = pluggers;
            pluggers = new PredefinedModule.FunctionPlugger[old.Length + 1];
            Array.Copy(old, 0, pluggers, 0, old.Length);
            pluggers[old.Length] = plugger;
        }

        /// <summary> Explicitly authorizes a Java class to be used as extension.
        /// <p>
        /// Caution: this is a security feature. By default, all classes are
        /// allowed. However, when this method is used once, the control becomes
        /// explicit and each class whose methods are used as extension functions
        /// must be declared.
        /// </summary>
        public virtual void authorizeJavaClass(string className) {
            javaPlugger.authorizeClass(className);
        }

        public override Function localFunctionLookup(QName name) {
            // caching:
            Function fun = (Function)functionMap[name];
            if (fun != null)
                return fun;
            // invoke pluggers:
            for (int p = 0; p < pluggers.Length; p++)
                if ((fun = pluggers[p].plug(name, this)) != null) {
                    declareFunction(fun);
                    return fun;
                }
            // maybe failure: but try again, as a Java class might have declared
            // the function in its plug hook
            return (Function)functionMap[name];
        }

        /// <summary> Basic implementation of function plugger: recognize function by NS and
        /// instantiate a pattern with the localname. The pattern can contain %n
        /// (unmodified name of the function), %c (camelcased name), %C (camelcased
        /// and capitalized name).
        /// </summary>
        public class BasicFunctionPlugger : PredefinedModule.FunctionPlugger {
            internal string ns;

            internal string pattern;

            public BasicFunctionPlugger(string ns, string pattern) {
                this.ns = NamespaceContext.unique(ns);
                this.pattern = pattern;
            }

            public virtual Function plug(QName functionName, PredefinedModule target) {
                // System.err.println(ns+" plug " +functionName);
                if ((System.Object)functionName.Namespace != (System.Object)ns) {
                    return null;
                }
                string name = functionName.Name;
                System.Text.StringBuilder className = new System.Text.StringBuilder(pattern.Length + name.Length);
                for (int c = 0, C = pattern.Length; c < C; c++) {
                    char ch = pattern[c];
                    if (ch != '%') {
                        className.Append(ch);
                        continue;
                    }
                    ch = pattern[++c];
                    if (ch == 'n')
                        className.Append(name);
                    else if (ch == 'C' || ch == 'c')
                        className.Append(Utils.camelCase(name, ch == 'C'));
                }
                try {
                    System.Type fclass = System.Type.GetType(className.ToString());
                    return (Function)System.Activator.CreateInstance(fclass);
                } catch (System.Exception ie) {
                    System.Console.Error.WriteLine("*** error instantiating function " + functionName);
                    SupportClass.WriteStackTrace(ie, Console.Error); // abnormal
                }
                return null;
            }
        }
        static PredefinedModule() {
            {
                // must be done creating predefined module (ie registering functions)
                string className = typeof(Function).FullName;
                // Dont use getPackage here, as it appears to be unimplemented
                // in some J2EE environments:
                funPackage = className.Substring(0, (className.LastIndexOf('.')) - (0));
            }
        }
    }
}